#pragma once
#include <dokan/dokan.h>
#include <dokan/fileinfo.h>

#include "BackupFSUtils.h"
#include "BackupFSCore.h"
#include <WinBase.h>
#include <atomic>
#include <filesystem>
#include <shared_mutex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <list>
/*
File node
A file node represents a file or directory and stores various attributes of the file, 
such as size, time, security attributes, and file blocks.
*/
namespace BackupFS 
{
#define FS_CORE BackupFSCore::GetInstance()

	// Safe class wrapping a Win32 Security Descriptor
	struct SecurityInformations : std::shared_mutex
	{
		std::unique_ptr<byte[]> Descriptor = nullptr;
		DWORD DescriptorSize = 0;

		SecurityInformations() = default;
		SecurityInformations(const SecurityInformations&) = delete;
		SecurityInformations& operator=(const SecurityInformations&) = delete;

		void SetDescriptor(PSECURITY_DESCRIPTOR securitydescriptor) 
		{
			if (!securitydescriptor)
			{
				return;
			}
			DescriptorSize = GetSecurityDescriptorLength(securitydescriptor);
			Descriptor = std::make_unique<byte[]>(DescriptorSize);
			memcpy(Descriptor.get(), securitydescriptor, DescriptorSize);
		}
	};

	// Contains file time metadata from a node
	// The information can safely be accessed from any thread.
	struct FileTimes
	{
		void Reset()
		{
			LastAccess = LastWrite = Creation = GetCurrentTime();
		}

		static bool Empty(const FILETIME* filetime)
		{
			return filetime->dwHighDateTime == 0 && filetime->dwLowDateTime == 0;
		}
		/*
		convert the current time to FILETIME
		In order to pursue better performance, time(NULL) is not used, but GetTickCount64() is used.
		*/
		static LONGLONG GetCurrentTime()
		{
			LONGLONG start_time = 0;
			LONGLONG start_tickcount = 0;
			BackupFSCore::GetInstance()->GetStartTime(start_time, start_tickcount);
			LONGLONG curr_time = start_time + (GetTickCount64() - start_tickcount) / 1000;
			FILETIME t;
			BackupFSUtils::TimetToFileTime(curr_time, &t);
			return BackupFSUtils::DDwLowHighToLlong(t.dwLowDateTime, t.dwHighDateTime);
		}

		std::atomic<LONGLONG> Creation;
		std::atomic<LONGLONG> LastAccess;
		std::atomic<LONGLONG> LastWrite;
	};

	class BackupFSFileNode
	{
	public:
		BackupFSFileNode(const std::wstring& filename, bool is_directory, DWORD file_attr,
			const PDOKAN_IO_SECURITY_CONTEXT security_context);

		BackupFSFileNode(const BackupFSFileNode& f) = delete;
		~BackupFSFileNode();
		DWORD Read(LPVOID buffer, LONGLONG bufferlength, LONGLONG offset);
		DWORD Write(LPCVOID buffer, LONGLONG number_of_bytes_to_write, LONGLONG offset);
		void Close(ULONG process_id);
		void Cleanup();
		const LONGLONG GetFileSize();
		void SetEndOfFile(const LONGLONG& byte_offset);
		void SetAllocatedSize(LONGLONG size) { _AllocatedSize = size; }
		const std::wstring GetFileName();
		void SetFileName(const std::wstring& filename);
		bool SetSecurity(PSECURITY_DESCRIPTOR security_descriptor);
		bool SetFileTime(LONGLONG create_time,LONGLONG write_time, LONGLONG access_time);
		bool LockRegion(LONGLONG offset, LONGLONG length, ULONG process_id);
		bool UnLockRegion(LONGLONG offset, LONGLONG length, ULONG process_id);
		bool IsLockedOffset(LONGLONG offset, ULONG process_id);
		// Alternated streams
		void AddStream(const std::shared_ptr<BackupFSFileNode>& stream);
		void RemoveStream(const std::shared_ptr<BackupFSFileNode>& stream);
		std::unordered_map<std::wstring, std::shared_ptr<BackupFSFileNode> > GetStreams();
		std::list<std::shared_ptr<DATA_BLOCK>>& GetDataBlocks() { return _DataBlocks; }
		bool IsDirectory = false;
		DWORD Attributes = 0;
		LONGLONG ParentId = 0;
		LONGLONG NodeId = 0;
		LONGLONG FileIndex = 0;
		std::shared_ptr<BackupFSFileNode> MainStream;
		FileTimes Times;
		SecurityInformations Security;
		LONGLONG FileSize = 0;
	private:
		DWORD _Update(LPCVOID buffer, LONGLONG number_of_bytes_to_write, LONGLONG offset);
		DWORD _Append(LPCVOID buffer, LONGLONG number_of_bytes_to_write, LONGLONG offset);
		bool _OffsetInBlock(const std::shared_ptr<DATA_BLOCK>& block, LONGLONG offset);
	private:
		BackupFSFileNode() = default;

		std::mutex _DataMutex;
		std::unordered_map<std::wstring, std::shared_ptr<BackupFSFileNode> > _Streams;
		std::shared_mutex _FileNameMutex;
		std::wstring _FileName;
		//can insert easily with list
		std::list<std::shared_ptr<DATA_BLOCK>>_DataBlocks;
		LONGLONG _AllocatedSize = 0;
		std::vector<std::shared_ptr<LOCK_REGION>>_LockedRegions;
	};
}  // namespace BackupFS

